//------------------------------------------------------------------------------------------------- 
// <copyright file="SearchConfigurationHelper.cs" company="Collaboris Ltd">
// Copyright (c) Collaboris Ltd.  All rights reserved.
// </copyright>
// <summary>Defines the SearchConfigurationHelper type.</summary>
//-------------------------------------------------------------------------------------------------
namespace Collaboris.SharePoint.Moss.Search
{
    using System;
    using System.IO;
    using System.Xml.Serialization;

    using Microsoft.Office.Server;
    using Microsoft.Office.Server.Search.Administration;
    using Microsoft.SharePoint;

    /// <summary>
    /// </summary>
    public class SearchConfigurationHelper 
    {
        #region Fields
        private readonly string sspName;
        private readonly SPSite site;
        #endregion

        #region Constructor

        public SearchConfigurationHelper(SPSite site, string sspName)
        {
            this.site = site;
            this.sspName = sspName;
        }

        #endregion

        #region Helper methods

        private static string ReplaceVariables(string xml, SPSite site)
        {
            string newXml = xml.Replace("{Site.Url}", site.Url);
            return newXml;
        }
       
        private SearchContext GetSearchContext(ScopeType scopeType)
        {
            ServerContext serverContext = null;
            string siteName = null;
            
            // Get the search context depending on the type of scope
            if (scopeType == ScopeType.Site)
            {
                // ensure that site is not null
                if (this.site == null)
                    throw new ArgumentNullException("site");

                serverContext = ServerContext.GetContext(this.site);
                siteName = this.site.Url;
            }

            if (scopeType == ScopeType.Shared)
            {
                //string sspGuid = SSPHelper.GetSSPUrl(site);
                
                // Ensure that an SSP name has been provided
                if (String.IsNullOrEmpty(this.sspName))
                    throw new ArgumentNullException("sspName","Cannot create share scope. SearchConfigurationHelper was initialised wihtout a valid SSP name");

                serverContext = ServerContext.GetContext(this.sspName);

                siteName = this.sspName;
            }

            if (serverContext == null)
                throw new ApplicationException("Error creating server context for site: " + siteName);
            
            return SearchContext.GetContext(serverContext);
        }
        
        private static void AddRulesToScope(Scope scope, SearchRulesCollection rules, SearchContext context)
        {
            foreach (SearchRule rule in rules)
            {
                
                switch (rule.RuleType)
                {
                    case RuleType.AllContentSearch:
                        scope.Rules.CreateAllContentRule();
                        break;

                    case RuleType.PropertyQuerySearch:
                        //Get the managed property
                        Schema schema = new Schema(context);
                        ManagedPropertyCollection allProperties = schema.AllManagedProperties;
                        ManagedProperty property = allProperties[rule.PropertyName];
                        scope.Rules.CreatePropertyQueryRule(rule.Behaviour, property, rule.Value);
                        break;

                    case RuleType.UrlSearch:
                        scope.Rules.CreateUrlRule(rule.Behaviour, rule.UrlType, rule.Url);
                        break;
                }              
            } 
        }
        
        private static Scope GetSearchScope(Scopes scopes, string scopeName, string siteUrl, ScopeType scopeType)
        {            
            // if it is a site collection scope
            if (scopeType == ScopeType.Site)
            {
                try
                {
                    return scopes.GetScope(new Uri(siteUrl), scopeName);
                }
                catch
                {
                    return null;
                }
            }

            // not a site collection scope so it has to be a shared scope
            try
            {
                return scopes.GetSharedScope(scopeName);
            }
            catch
            {
                return null;
            }
        }
        
        private void AddSearchScope(SearchScope newScope)
        {
            // ensure a new scope is passed in
            if (newScope == null)
                throw new ArgumentNullException("newScope");

            SearchContext searchContext = this.GetSearchContext(newScope.Type);
            if (searchContext == null)
                throw new ApplicationException("Error creating search context");

            // Get the collection of scopes
            Scopes scopes = new Scopes(searchContext);

            // Gets the scope
            Scope scope = GetSearchScope(scopes, newScope.Name, this.site.Url, newScope.Type);

            // If scope already exists and the new scope was marked to replace then delete the existing
            if (scope != null && newScope.Replace)
            {
                scope.Delete();
                scope = null;
            }

            // if the new scope is a shared scope then the site URL has to be null
            string siteUrl = (newScope.Type == ScopeType.Site) ? this.site.Url : null;

            Uri siteUri = null;

            if (siteUrl != null)
                siteUri = new Uri(siteUrl);

            // if scope does not exist then create it
            if (scope == null)
                scope = scopes.AllScopes.Create(newScope.Name, newScope.Description, siteUri, true,
                                                newScope.ResultsPage, ScopeCompilationType.AlwaysCompile);

            AddRulesToScope(scope, newScope.Rules, searchContext);

            scopes.StartCompilation();
            scopes.Update();
        }


        private void AddSearchScopes(SearchScopeCollection collection)
        {
            foreach (SearchScope scope in collection)
            {
                this.AddSearchScope(scope);
            }
        }


        private ScopeDisplayGroup GetDisplayGroup(string name, ScopeDisplayGroupCollection scopeDisplayGroups)
        {
            foreach (ScopeDisplayGroup group in scopeDisplayGroups)
            {
                if ((group.Name == name) && (group.OwningSite.Url == this.site.Url))
                    return group;
            }

            return null;
        }       

        private void AddDisplayGroup(DisplayGroup group)
        {                       
            SearchContext searchContext = this.GetSearchContext(ScopeType.Site);
            // SearchContext searchContext = SearchContext.GetContext(currentSiteCollection);
            
            // Get the collection of display groups
            Scopes scopes = new Scopes(searchContext);
            ScopeDisplayGroupCollection scopeDisplayGroups = scopes.AllDisplayGroups;

            // check if display group already exists
            // ScopeDisplayGroup scopeDisplayGroup = GetDisplayGroup(group.Name, scopeDisplayGroups);
            ScopeDisplayGroup scopeDisplayGroup = scopes.GetDisplayGroup(new Uri(this.site.Url), group.Name);

            // display group doe not exist then create a new one
            if (scopeDisplayGroup == null)
                scopeDisplayGroup = scopeDisplayGroups.Create(group.Name, group.Description, new Uri(this.site.Url), group.Display);
 
            // assign the scopes to the new display group
            foreach (DisplayScope displayScope in group.DisplayScopes)
            {
                Scope scope = null;

                if (displayScope.Type == ScopeType.Shared)
                    scope = scopes.GetSharedScope(displayScope.Name);
                else
                    scope = scopes.GetScope(new Uri(this.site.RootWeb.Url), displayScope.Name);
               
                if (!scopeDisplayGroup.Contains(scope))
                    scopeDisplayGroup.Add(scope);

                if (displayScope.Default == true)
                    scopeDisplayGroup.Default = scope;
            }

            scopeDisplayGroup.Update();
        }
                
        private void AddDisplayGroups(DisplayGroupCollection groups)
        {
            foreach (DisplayGroup group in groups)
            {
                this.AddDisplayGroup(group);
            }           
        }

        #endregion

        #region Public methods

        /// <summary>
        /// 
        /// </summary>
        /// <param name="searchConfiguration"></param>
        public void Configure(SearchConfiguration searchConfiguration)
        {
            // Configure search scopes
            this.AddSearchScopes(searchConfiguration.SearchScopes);

            // Add display groups
            this.AddDisplayGroups(searchConfiguration.DisplayGroups);
        }

        /// <summary>
        /// Deserialises the search scopes
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        public SearchConfiguration Deserialize(string filename)
        {
            // Read the XML
            XmlSerializer s = new XmlSerializer(typeof(SearchConfiguration));

            string xml = File.ReadAllText(filename);
            xml = ReplaceVariables(xml, this.site);

            // Deserialise
            StringReader sr = new StringReader(xml);
            SearchConfiguration searchConfiguration = (SearchConfiguration)s.Deserialize(sr);
            sr.Close();

            return searchConfiguration;          
        }        
        #endregion
    }
}